#ifndef SamplerState_h
#define SamplerState_h

#include <Defines.h>
#include <CSharpPropertyMethods.h>

// Riccsson includes.
#include <System/Object.h>
#include <System/Type.h>
#include <System/ArgumentException.h>
#include <System/ArgumentNullException.h>
#include <System/Array.h>
#include <System/Activator.h>

// XNA includes.
#include "../GraphicsResource.h"
#include "TextureAddressMode.h"
#include "TextureFilter.h"

namespace Riccsson
{
	namespace Xna
	{
		namespace Framework
		{
			namespace Graphics
			{
				class SamplerState : public GraphicsResource
				{
					
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, AnisotropicClamp)
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, AnisotropicWrap)
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, LinearClamp)
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, LinearWrap)
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, PointClamp)
					PROP3_STATIC(SamplerState, GC_PTR<SamplerState>, PointWrap)


			#if DIRECTX
					private: SharpDX.Direct3D11.SamplerState _state;
			#endif

			#if OPENGL
					private: static float MaxTextureMaxAnisotropy = 4;
			#if GLES
					private: const GetPName GetPNameMaxTextureMaxAnisotropy = (GetPName)All.MaxTextureMaxAnisotropyExt;
					private: const TextureParameterName TextureParameterNameTextureMaxAnisotropy = (TextureParameterName)All.TextureMaxAnisotropyExt;
			#else
					private: const GetPName GetPNameMaxTextureMaxAnisotropy = (GetPName)ExtTextureFilterAnisotropic.MaxTextureMaxAnisotropyExt;
					private: const TextureParameterName TextureParameterNameTextureMaxAnisotropy = (TextureParameterName)ExtTextureFilterAnisotropic.TextureMaxAnisotropyExt;
			#endif
			#endif

					//public: static /*readonly*/ GC_PTR<SamplerState> AnisotropicClamp;
					//public: static /*readonly*/ GC_PTR<SamplerState> AnisotropicWrap;
					//public: static /*readonly*/ GC_PTR<SamplerState> LinearClamp;
					//public: static /*readonly*/ GC_PTR<SamplerState> LinearWrap;
					//public: static /*readonly*/ GC_PTR<SamplerState> PointClamp;
					//public: static /*readonly*/ GC_PTR<SamplerState> PointWrap;
							
					public: TextureAddressMode AddressU;
					public: TextureAddressMode AddressV;
					public: TextureAddressMode AddressW;
					public: TextureFilter Filter;
		
					public: int MaxAnisotropy;
					public: int MaxMipLevel;
					public: float MipMapLevelOfDetailBias;

							static void static_SamplerState () 
					{
			#if OPENGL
			#if GLES
						if (GraphicsCapabilities.TextureFilterAnisotric)
						{
							GL.GetFloat(GetPNameMaxTextureMaxAnisotropy, ref SamplerState.MaxTextureMaxAnisotropy);
						}
			#else
						GL.GetFloat(GetPNameMaxTextureMaxAnisotropy, out SamplerState.MaxTextureMaxAnisotropy);
			#endif
						GraphicsExtensions.CheckGLError();
			#endif

						AnisotropicClamp = new SamplerState ();
						PointWrap->Filter = TextureFilter::Anisotropic;
						PointWrap->AddressU = TextureAddressMode::Clamp;
						PointWrap->AddressV = TextureAddressMode::Clamp;
						PointWrap->AddressW = TextureAddressMode::Clamp;
			
						AnisotropicWrap = new SamplerState ();
						PointWrap->Filter = TextureFilter::Anisotropic;
						PointWrap->AddressU = TextureAddressMode::Wrap;
						PointWrap->AddressV = TextureAddressMode::Wrap;
						PointWrap->AddressW = TextureAddressMode::Wrap;
			
						LinearClamp = new SamplerState ();
						PointWrap->Filter = TextureFilter::Linear;
						PointWrap->AddressU = TextureAddressMode::Clamp;
						PointWrap->AddressV = TextureAddressMode::Clamp;
						PointWrap->AddressW = TextureAddressMode::Clamp;
			
						LinearWrap = new SamplerState ();
						PointWrap->Filter = TextureFilter::Linear;
						PointWrap->AddressU = TextureAddressMode::Wrap;
						PointWrap->AddressV = TextureAddressMode::Wrap;
						PointWrap->AddressW = TextureAddressMode::Wrap;
			
						PointClamp = new SamplerState ();
						PointWrap->Filter = TextureFilter::Point;
						PointWrap->AddressU = TextureAddressMode::Clamp;
						PointWrap->AddressV = TextureAddressMode::Clamp;
						PointWrap->AddressW = TextureAddressMode::Clamp;
			
						PointWrap = new SamplerState (); 
						PointWrap->Filter = TextureFilter::Point;
						PointWrap->AddressU = TextureAddressMode::Wrap;
						PointWrap->AddressV = TextureAddressMode::Wrap;
						PointWrap->AddressW = TextureAddressMode::Wrap;
					}

					public: SamplerState()
					{
						this->Filter = TextureFilter::Linear;
						this->AddressU = TextureAddressMode::Wrap;
						this->AddressV = TextureAddressMode::Wrap;
						this->AddressW = TextureAddressMode::Wrap;
						this->MaxAnisotropy = 4;
						this->MaxMipLevel = 0;
						this->MipMapLevelOfDetailBias = 0.0f;
					}
							
					SamplerState(const SamplerState& other)
					{	
						this->Filter = other.Filter;
						this->AddressU = other.AddressU;
						this->AddressV = other.AddressV;
						this->AddressW = other.AddressW;
						this->MaxAnisotropy = other.MaxAnisotropy;
						this->MaxMipLevel = other.MaxMipLevel;
						this->MipMapLevelOfDetailBias = other.MipMapLevelOfDetailBias;
					}
		
			#if DIRECTX

					internal SharpDX.Direct3D11.SamplerState GetState(GraphicsDevice device)
					{
						if (_state == null)
						{
							// We're now bound to a device... no one should
							// be changing the state of this object now!
							GraphicsDevice = device;

							// Build the description.
							var desc = new SharpDX.Direct3D11.SamplerStateDescription();

							desc.AddressU = GetAddressMode(AddressU);
							desc.AddressV = GetAddressMode(AddressV);
							desc.AddressW = GetAddressMode(AddressW);

							desc.Filter = GetFilter(Filter);
							desc.MaximumAnisotropy = MaxAnisotropy;
							desc.MipLodBias = MipMapLevelOfDetailBias;

							// TODO: How do i do these?
							desc.MinimumLod = 0.0f;
							desc.BorderColor = new SharpDX.Color4(0, 0, 0, 0);

							// To support feature level 9.1 these must 
							// be set to these exact values.
							desc.MaximumLod = float.MaxValue;
							desc.ComparisonFunction = SharpDX.Direct3D11.Comparison.Never;

							// Create the state.
							_state = new SharpDX.Direct3D11.SamplerState(GraphicsDevice._d3dDevice, ref desc);
						}

						Debug.Assert(GraphicsDevice == device, "The state was created for a different device!");

						return _state;
					}

					private: static SharpDX.Direct3D11.Filter GetFilter(TextureFilter filter)
					{
						switch (filter)
						{
							case TextureFilter.Anisotropic:
								return SharpDX.Direct3D11.Filter.Anisotropic;

							case TextureFilter.Linear:
								return SharpDX.Direct3D11.Filter.MinMagMipLinear;

							case TextureFilter.LinearMipPoint:
								return SharpDX.Direct3D11.Filter.MinMagLinearMipPoint;

							case TextureFilter.MinLinearMagPointMipLinear:
								return SharpDX.Direct3D11.Filter.MinLinearMagPointMipLinear;

							case TextureFilter.MinLinearMagPointMipPoint:
								return SharpDX.Direct3D11.Filter.MinLinearMagMipPoint;

							case TextureFilter.MinPointMagLinearMipLinear:
								return SharpDX.Direct3D11.Filter.MinPointMagMipLinear;

							case TextureFilter.MinPointMagLinearMipPoint:
								return SharpDX.Direct3D11.Filter.MinPointMagLinearMipPoint;

							case TextureFilter.Point:
								return SharpDX.Direct3D11.Filter.MinMagMipPoint;

							case TextureFilter.PointMipLinear:
								return SharpDX.Direct3D11.Filter.MinMagPointMipLinear;

							default:
								throw new NotImplementedException("Invalid texture filter!");
						}
					}

					private: static SharpDX.Direct3D11.TextureAddressMode GetAddressMode(TextureAddressMode mode)
					{
						switch (mode)
						{
							case TextureAddressMode.Clamp:
								return SharpDX.Direct3D11.TextureAddressMode.Clamp;

							case TextureAddressMode.Mirror:
								return SharpDX.Direct3D11.TextureAddressMode.Mirror;

							case TextureAddressMode.Wrap:
								return SharpDX.Direct3D11.TextureAddressMode.Wrap;

							default:
								throw new NotImplementedException("Invalid texture address mode!");
						}
					}

			#endif // DIRECTX


			#if OPENGL

					internal void Activate(TextureTarget target, bool useMipmaps = false)
					{
						switch (Filter)
						{
						case TextureFilter.Point:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.NearestMipmapNearest : TextureMinFilter.Nearest));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.Linear:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.LinearMipmapLinear : TextureMinFilter.Linear));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.Anisotropic:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, MathHelper.Clamp(this.MaxAnisotropy, 1.0f, SamplerState.MaxTextureMaxAnisotropy));
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.LinearMipmapLinear : TextureMinFilter.Linear));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.PointMipLinear:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.NearestMipmapLinear : TextureMinFilter.Nearest));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.LinearMipPoint:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.LinearMipmapNearest : TextureMinFilter.Linear));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.MinLinearMagPointMipLinear:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.LinearMipmapLinear : TextureMinFilter.Linear));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.MinLinearMagPointMipPoint:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.LinearMipmapNearest: TextureMinFilter.Linear));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.MinPointMagLinearMipLinear:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.NearestMipmapLinear: TextureMinFilter.Nearest));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
							GraphicsExtensions.CheckGLError();
							break;
						case TextureFilter.MinPointMagLinearMipPoint:
							if (GraphicsCapabilities.TextureFilterAnisotric)
							{
								GL.TexParameter(target, TextureParameterNameTextureMaxAnisotropy, 1.0f);
								GraphicsExtensions.CheckGLError();
							}
							GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)(useMipmaps ? TextureMinFilter.NearestMipmapNearest: TextureMinFilter.Nearest));
							GraphicsExtensions.CheckGLError();
							GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
							GraphicsExtensions.CheckGLError();
							break;
						default:
							throw new NotImplementedException();
						}

						// Set up texture addressing.
						GL.TexParameter(target, TextureParameterName.TextureWrapS, (int)GetWrapMode(AddressU));
						GraphicsExtensions.CheckGLError();
						GL.TexParameter(target, TextureParameterName.TextureWrapT, (int)GetWrapMode(AddressV));
						GraphicsExtensions.CheckGLError();
					}

					private: int GetWrapMode(TextureAddressMode textureAddressMode)
					{
						switch(textureAddressMode)
						{
						case TextureAddressMode.Clamp:
							return (int)TextureWrapMode.ClampToEdge;
						case TextureAddressMode.Wrap:
							return (int)TextureWrapMode.Repeat;
						case TextureAddressMode.Mirror:
							return (int)TextureWrapMode.MirroredRepeat;
						default:
							throw new NotImplementedException("No support for " + textureAddressMode);
						}
					}

			#endif // OPENGL

				};
			}
		}
	}
}

#endif
